Skip to content

[FEATURE] 강사 시안 수정 요청시 메일 알림 이벤트 추가#102

Merged
fervovita merged 1 commit into
devfrom
feat/#100-instructor-revision-event
Jul 7, 2026
Merged

[FEATURE] 강사 시안 수정 요청시 메일 알림 이벤트 추가#102
fervovita merged 1 commit into
devfrom
feat/#100-instructor-revision-event

Conversation

@fervovita

@fervovita fervovita commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

🚀 Related issue

Closes #100

#️⃣ Summary

  • 강사가 시안 수정 요청시 디자이너에게 알림 메일을 발송을 추가했습니다.

🔧 Changes

  • 디자이너 알림 발송을 위한 RevisionRequestedEvent 추가
  • 디자이너용 메일 템플릿 revision-requested-designer.html 추가

📸 Test Evidence

  • swagger나 postman 결과를 캡쳐하여 첨부

💬 Reviewer Notes

  • 리뷰어나 같이 작업하는 사람들에게 남길 코멘트

Summary by CodeRabbit

  • New Features

    • 수정 요청이 생성되면 디자이너에게 알림 메일 발송이 예약되도록 지원합니다.
    • 수정 요청 안내 메일에 외주명, 디자이너명, 현재 수정 차수 등의 정보가 포함됩니다.
    • 수정 요청 확인용 이메일 템플릿과 버튼이 추가되었습니다.
  • Bug Fixes

    • 수정 요청 처리 시, 커미션 소유 여부와 배정 디자이너 정보를 함께 확인해 더 정확하게 처리합니다.

@fervovita fervovita self-assigned this Jul 6, 2026
@fervovita fervovita requested a review from Jong0128 as a code owner July 6, 2026 15:42
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

강사가 시안 수정을 요청하면 디자이너에게 메일 알림을 예약 발송하는 기능이 추가되었다. RevisionRequestedEvent 레코드, 강사/배정 디자이너 조회 서비스 메서드, 이벤트 발행 로직이 InstructorRevisionFacade에 추가되었고, RevisionRequestedNotifier가 이벤트를 받아 NotificationOutbox를 저장하며, 관련 메일 템플릿이 신규 작성되었다.

Changes

수정 요청 알림 기능

Layer / File(s) Summary
이벤트 데이터 계약
.../event/RevisionRequestedEvent.java
커미션/디자이너 정보, 현재 리비전 카운트, 메일 예약 시각을 포함하는 이벤트 레코드가 추가됨.
커미션 조회 확장 및 이벤트 발행
.../service/InstructorCommissionService.java, .../facade/InstructorRevisionFacade.java
강사/배정 디자이너를 함께 조회하는 신규 서비스 메서드가 추가되고, InstructorRevisionFacade의 createRevision이 해당 메서드를 사용하도록 변경되며, 수정 요청 저장 후 디자이너 정보와 계산된 수정 차수를 담아 RevisionRequestedEvent를 발행하는 로직이 추가됨.
알림 아웃박스 등록 및 메일 템플릿
.../notification/RevisionRequestedNotifier.java, templates/email/revision-requested-designer.html
RevisionRequestedEvent를 수신해 메일 제목/템플릿 키와 데이터를 구성한 NotificationOutbox를 저장하는 컴포넌트와, 디자이너용 수정 요청 안내 메일 HTML 템플릿이 추가됨.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Instructor
  participant InstructorRevisionFacade
  participant InstructorCommissionService
  participant EventPublisher
  participant RevisionRequestedNotifier
  participant NotificationOutboxRepository

  Instructor->>InstructorRevisionFacade: createRevision(commissionId, ...)
  InstructorRevisionFacade->>InstructorCommissionService: getOwnedCommissionWithInstructorAndAssignedDesigner(commissionId, instructorId)
  InstructorCommissionService-->>InstructorRevisionFacade: Commission(designer 정보 포함)
  InstructorRevisionFacade->>InstructorRevisionFacade: publishRevisionRequestedEvent(...)
  InstructorRevisionFacade->>EventPublisher: publishEvent(RevisionRequestedEvent)
  EventPublisher->>RevisionRequestedNotifier: onRevisionRequested(event)
  RevisionRequestedNotifier->>NotificationOutboxRepository: save(NotificationOutbox)
Loading

Possibly related PRs

  • Ditda-Official/Ditda-Backend#22: 리비전 요청 생성 시 사용되는 RevisionRequest/RevisionDetail 도메인 엔티티를 도입한 PR로, 본 PR의 이벤트 발행 지점과 직접 연결됨.
  • Ditda-Official/Ditda-Backend#60: NotificationOutbox/스케줄러/메일 인프라를 추가한 PR로, 본 PR의 RevisionRequestedNotifier가 이를 직접 사용함.
  • Ditda-Official/Ditda-Backend#93: InstructorRevisionFacade의 createRevision 흐름을 수정한 PR로, 본 PR의 이벤트 발행 로직 추가와 동일 지점에서 연관됨.

Poem

  • 토끼가 붓을 놓고 알림을 심어요 🐰
  • 수정 요청이 날아가면 메일도 함께 날아가요 ✉️
  • 디자이너의 편지함에 살짝 도착하죠
  • 아웃박스에 곱게 담아 예약해두고
  • 시안 수정 소식, 이제 놓치지 않아요 🎨
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 강사 시안 수정 요청 시 디자이너 메일 알림 이벤트 추가라는 핵심 변경을 정확히 요약합니다.
Linked Issues check ✅ Passed 이슈 #100의 핵심인 수정 요청 시 디자이너 알림 이벤트 발행과 메일 연결, 템플릿 추가를 모두 충족합니다.
Out of Scope Changes check ✅ Passed 추가된 변경은 수정 요청 알림 메일 기능 구현에 필요한 이벤트, 수신 처리, 템플릿에 한정됩니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#100-instructor-revision-event

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java (1)

14-27: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

알림 실패가 핵심 트랜잭션을 롤백시킬 수 있음.

@EventListener는 발행자(InstructorRevisionFacade.createRevision, @Transactional)의 트랜잭션 내에서 동기 실행됩니다. outboxRepository.save(...)에서 예외가 발생하면 수정 요청 저장 자체가 롤백되어, 메일 아웃박스 저장 실패가 핵심 비즈니스 로직 실패로 이어집니다. @TransactionalEventListener(phase = AFTER_COMMIT)으로 전환해 커밋 성공 이후에만 알림을 발행하도록 분리하는 것을 권장합니다.

🔧 제안 수정
-import org.springframework.context.event.EventListener;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.event.TransactionPhase;
+import org.springframework.transaction.event.TransactionalEventListener;

 `@Component`
 `@RequiredArgsConstructor`
 public class RevisionRequestedNotifier {

 	private final NotificationOutboxRepository outboxRepository;

-	`@EventListener`
+	`@TransactionalEventListener`(phase = TransactionPhase.AFTER_COMMIT)
 	public void onRevisionRequested(RevisionRequestedEvent event) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java`
around lines 14 - 27, `RevisionRequestedNotifier.onRevisionRequested` is
currently using `@EventListener`, so `NotificationOutboxRepository.save(...)`
runs inside the caller’s transaction and can roll back the main use case on
notification failure. Change the listener to `@TransactionalEventListener(phase
= AFTER_COMMIT)` so it only runs after the transaction from
`InstructorRevisionFacade.createRevision` commits successfully, and keep the
outbox save logic in `registerDesignerRevisionRequested`/`onRevisionRequested`
unchanged aside from the listener annotation.
src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java (1)

101-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

소유권 검증 로직 중복.

getOwnedCommissiongetOwnedCommissionWithInstructorAndAssignedDesigner가 조회 방식만 다르고 존재/소유권 검증 로직이 동일합니다. 검증 정책 변경 시 두 메서드를 모두 수정해야 하는 부담이 있습니다.

♻️ 공통 검증 로직 추출 예시
+	private Commission validateOwnership(Commission commission, Long instructorId) {
+		if (!commission.isOwnedBy(instructorId)) {
+			throw new GeneralException(CommissionErrorCode.COMMISSION_ACCESS_DENIED);
+		}
+		return commission;
+	}
+
 	public Commission getOwnedCommission(Long commissionId, Long instructorId) {
 		Commission commission = commissionRepository.findById(commissionId)
 			.orElseThrow(() -> new GeneralException(CommissionErrorCode.COMMISSION_NOT_FOUND));
-		if (!commission.isOwnedBy(instructorId)) {
-			throw new GeneralException(CommissionErrorCode.COMMISSION_ACCESS_DENIED);
-		}
-		return commission;
+		return validateOwnership(commission, instructorId);
 	}

 	public Commission getOwnedCommissionWithInstructorAndAssignedDesigner(Long commissionId, Long instructorId) {
 		Commission commission = commissionRepository.findWithInstructorAndAssignedDesignerById(commissionId)
 			.orElseThrow(() -> new GeneralException(CommissionErrorCode.COMMISSION_NOT_FOUND));
-		if (!commission.isOwnedBy(instructorId)) {
-			throw new GeneralException(CommissionErrorCode.COMMISSION_ACCESS_DENIED);
-		}
-		return commission;
+		return validateOwnership(commission, instructorId);
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java`
around lines 101 - 126, `InstructorCommissionService`의 `getOwnedCommission`과
`getOwnedCommissionWithInstructorAndAssignedDesigner`에 있는 존재 확인 및
`commission.isOwnedBy(instructorId)` 소유권 검증이 중복됩니다. 공통 검증 흐름을 하나의 private
helper로 추출하고, 두 public 메서드는 조회 방식만 다르게 한 뒤 그 helper를 재사용하도록 정리하세요.
`CommissionRepository`, `findById`, `findWithInstructorAndAssignedDesignerById`,
`GeneralException`, `CommissionErrorCode`를 기준으로 위치를 찾으면 됩니다.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java`:
- Around line 14-27: `RevisionRequestedNotifier.onRevisionRequested` is
currently using `@EventListener`, so `NotificationOutboxRepository.save(...)`
runs inside the caller’s transaction and can roll back the main use case on
notification failure. Change the listener to `@TransactionalEventListener(phase
= AFTER_COMMIT)` so it only runs after the transaction from
`InstructorRevisionFacade.createRevision` commits successfully, and keep the
outbox save logic in `registerDesignerRevisionRequested`/`onRevisionRequested`
unchanged aside from the listener annotation.

In
`@src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java`:
- Around line 101-126: `InstructorCommissionService`의 `getOwnedCommission`과
`getOwnedCommissionWithInstructorAndAssignedDesigner`에 있는 존재 확인 및
`commission.isOwnedBy(instructorId)` 소유권 검증이 중복됩니다. 공통 검증 흐름을 하나의 private
helper로 추출하고, 두 public 메서드는 조회 방식만 다르게 한 뒤 그 helper를 재사용하도록 정리하세요.
`CommissionRepository`, `findById`, `findWithInstructorAndAssignedDesignerById`,
`GeneralException`, `CommissionErrorCode`를 기준으로 위치를 찾으면 됩니다.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: bddb219a-1855-4804-aa9e-825aedaea374

📥 Commits

Reviewing files that changed from the base of the PR and between 5344357 and d64b21a.

📒 Files selected for processing (5)
  • src/main/java/ditda/backend/domain/commission/core/event/RevisionRequestedEvent.java
  • src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java
  • src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java
  • src/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.java
  • src/main/resources/templates/email/revision-requested-designer.html

@Jong0128 Jong0128 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!!
바로 merge하셔도 좋을꺼같아요 😃

@fervovita fervovita merged commit 85b993c into dev Jul 7, 2026
2 checks passed
@fervovita fervovita deleted the feat/#100-instructor-revision-event branch July 7, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 강사 시안 수정 요청시 메일 알림 이벤트 추가

2 participants